/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_buffer.c
 *
 * Purpose: implementation a buffer wrapper for cbus.
 *
 * Developer:
 *   wen.gu , 2022-01-18
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "cbus/core/cbus_buffer.h"

#include <string.h>

#include "cbus/core/cbus_atomic.h"

/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

typedef struct _buffer_ref
{   cbus_atomic_int ref_count; 
    GU32 size;
    GBUF data;
    void* opaque;
    cbus_buffer_data_free_func on_free;    
}buffer_ref_t;

struct _cbus_buffer
{
    buffer_ref_t* ref;
};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/
static buffer_ref_t* buffer_ref_create(GBUF data, GU32 size, cbus_buffer_data_free_func on_free, void* opaque)
{
    buffer_ref_t* ref = (buffer_ref_t*)malloc(sizeof(struct _buffer_ref));

    if (ref)
    {
        ref->ref_count = 0;
        ref->data = data;
        ref->size = size;
        ref->opaque = opaque;
        ref->on_free = on_free;
    }

    return ref;
}

static void buffer_ref_destroy(buffer_ref_t* ref)
{
    free(ref);
}

static void buffer_ref_add(buffer_ref_t* ref)
{
    cbus_atomic_increment(&ref->ref_count);
}

static void buffer_ref_release(buffer_ref_t* ref)
{
    if (cbus_atomic_decrement(&ref->ref_count) == 1)
    {
        ref->on_free(ref->opaque, ref->data, ref->size);
        free(ref);
    }
}


/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
cbus_buffer_t cbus_buffer_alloc(GU32 size)
{
    if (size == 0)
    {
        return CBUS_BUFFER_INVALID;
    }

    GBUF data = (GBUF)malloc(size);

    if (!data)
    {
        return CBUS_BUFFER_INVALID;
    }

    cbus_buffer_t buf = cbus_buffer_create(data, size, cbus_buffer_default_data_free, NULL);

    if (!buf)
    {
        free(data);
    }
    return buf;
}

cbus_buffer_t cbus_buffer_allocz(GU32 size)
{
    if (size == 0)
    {
        return CBUS_BUFFER_INVALID;
    }

    GBUF data = (GBUF)calloc(size, 1);

    if (!data)
    {
        return CBUS_BUFFER_INVALID;
    }

    cbus_buffer_t buf = cbus_buffer_create(data, size, cbus_buffer_default_data_free, NULL);

    if (!buf)
    {
        free(data);
    }

    return buf;
}

cbus_buffer_t cbus_buffer_create(GBUF data, GU32 size, cbus_buffer_data_free_func data_free, void* opaque)
{
    if (!data || (size == 0))
    {
        return CBUS_BUFFER_INVALID;
    }
    buffer_ref_t* buf_ref = buffer_ref_create(data, size, data_free ? data_free : cbus_buffer_default_data_free, opaque);

    if (!buf_ref)
    {
        return CBUS_BUFFER_INVALID;
    }

    cbus_buffer_t buf = (cbus_buffer_t)malloc(sizeof(struct _cbus_buffer));

    if (!buf)
    {
        buffer_ref_destroy(buf_ref);
        return CBUS_BUFFER_INVALID;
    }
    else
    {
        buf->ref = buf_ref;
    }

    return buf;
}

void cbus_buffer_default_data_free(void* opaque, GBUF data, GU32 size)
{
    free(data);
}

/** will be aways free old_buf and set it as 'CBUS_BUFFER_INVALID' whether realloc success or failed */
cbus_buffer_t cbus_buffer_realloc(cbus_buffer_t* old_buf, GU32 new_size) 
{
    if (!old_buf || (*old_buf == CBUS_BUFFER_INVALID))
    {
        return cbus_buffer_alloc(new_size);
    }

    cbus_buffer_t org_buf = *old_buf;

    if (new_size == org_buf->ref->size)
    {
        *old_buf = CBUS_BUFFER_INVALID;
        return org_buf;
    }

    if (new_size == 0)
    {
        cbus_buffer_free(org_buf);
        *old_buf = CBUS_BUFFER_INVALID;
        return CBUS_BUFFER_INVALID;
    }


    GBUF new_data = (GBUF)realloc(org_buf->ref->data, new_size);

    if (!new_data)
    {
        cbus_buffer_free(org_buf);
        *old_buf = CBUS_BUFFER_INVALID;
        return CBUS_BUFFER_INVALID;        
    }

    org_buf->ref->data = new_data;
    org_buf->ref->size = new_size;

    *old_buf = CBUS_BUFFER_INVALID;

    return org_buf;

}

void cbus_buffer_free(cbus_buffer_t buffer)
{
    if (buffer)
    {
        buffer_ref_release(buffer);

        free(buffer);
    }
}

cbus_buffer_t cbus_buffer_clone(cbus_buffer_t org_buf)
{
    if (!org_buf)
    {
        return CBUS_BUFFER_INVALID;
    }

    cbus_buffer_t buf = cbus_buffer_alloc(org_buf->ref->size);

    if (buf)
    {
        memcpy(buf->ref->data, org_buf->ref->data, org_buf->ref->size);
    }

    return buf;
}

cbus_buffer_t cbus_buffer_reference(cbus_buffer_t org_buf)
{
    if (!org_buf)
    {
        return NULL;
    }

    cbus_buffer_t buf = (cbus_buffer_t)malloc(sizeof(struct _cbus_buffer));

    if (buf)
    {
        buffer_ref_add(org_buf->ref);
        buf->ref = org_buf->ref;
    }

    return buf;
}


GBUF cbus_buffer_data(const cbus_buffer_t buffer)
{
    return (buffer && buffer->ref) ? (buffer->ref->data) : NULL;
}

GU32 cbus_buffer_size(const cbus_buffer_t buffer)
{
    return (buffer && buffer->ref) ? (buffer->ref->size) : 0;
}



